package com.h2cloud.rpc._4_rpc_framework;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Package: com.h2cloud.rpc._4_rpc_framework
 * @ClassName: RpcFramework
 * @Author: h2cloud
 * @CreateTime: 2022/8/14 12:33 下午
 * @Description:
 */
public class RpcFramework {

	/**
	 * 暴露服务
	 *
	 * @param service 服务实现
	 * @param port    服务端口
	 * @throws Exception
	 */
	public static void export(final Object service, int port) throws Exception {
		System.out.println("Export service " + service.getClass().getName() + " on port " + port);
		ServerSocket server = new ServerSocket(port);
		while (true) {
			try {
				final Socket socket = server.accept();
				new Thread(() -> {
					try (ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
						 ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
					) {
						String methodName = input.readUTF();
						Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
						Object[] arguments = (Object[]) input.readObject();

						Method method = service.getClass().getMethod(methodName, parameterTypes);
						Object result = method.invoke(service, arguments);

						output.writeObject(result);

					} catch (IOException | ClassNotFoundException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
						e.printStackTrace();
					}
				}).start();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 引用服务
	 *
	 * @param <T>            接口泛型
	 * @param interfaceClass 接口类型
	 * @param host           服务器主机名
	 * @param port           服务器端口
	 * @return 远程服务
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception {
		return (T) Proxy.newProxyInstance(
				interfaceClass.getClassLoader(),
				new Class<?>[]{interfaceClass},
				(proxy, method, arguments) -> {

					try (Socket socket = new Socket(host, port);
						 ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
						 ObjectInputStream input = new ObjectInputStream(socket.getInputStream())) {

						output.writeUTF(method.getName());
						output.writeObject(method.getParameterTypes());
						output.writeObject(arguments);

						Object result = input.readObject();
						if (result instanceof Throwable) {
							throw (Throwable) result;
						}
						return result;
					}
				});
	}

}

